#ifndef KPEXTDESCRIPTORMGR_H
#define KPEXTDESCRIPTORMGR_H

#include "kpextCommonDef.h"

namespace kpext{

//------------------ class declare -------------------------

class IGetDecriptorInfo;
class DescriptorPool;

//----------------------------------------------------------

/**
 * @brief 描述符信息获取接口类
 */
class IGetDecriptorInfo{
public:
    virtual ~IGetDecriptorInfo(){}

    /**
     * @brief 设置描述符信息绑定号
     * @param binding
     */
    virtual void setDescBinding(uint32_t binding){
        m_descBinding = binding;
    }

    /**
     * @brief 设置描述符信息绑定的数组长度
     * @param len
     */
    virtual void setBindingArrayLen(uint32_t len){
        m_descBindingArrayLen = len;
    }

    /**
     * @brief 设置描述符信息绑定的类型
     * @param type
     */
    virtual void setDescType(vk::DescriptorType type){
        m_descType = type;
    }

    /**
     * @brief 设置描述符信息绑定用于哪个着色器阶段
     * @param stage
     */
    virtual void setDescStage(vk::ShaderStageFlags stage){
        m_descStage = stage;
    }

    /**
     * @brief 获取描述符绑定信息
     * @return vulkan 描述符绑定信息
     */
    virtual vk::DescriptorSetLayoutBinding
    getDescriptorSetLayoutBinding() = 0;

    /**
     * @brief 获取vk::DescriptorBufferInfo对象共享指针
     * @return 成功返回vk::DescriptorBufferInfo共享对象指针，否则返回空指针
     */
    virtual std::shared_ptr<vk::DescriptorBufferInfo>
    getDescriptorBufferInfo(){
        return nullptr;
    }

    /**
     * @brief 获取vk::BufferView对象共享指针
     * @return 成功返回vk::BufferView共享对象指针，否则返回空指针
     */
    virtual std::shared_ptr<vk::BufferView> getBufferView(){
        return nullptr;
    }

    /**
     * @brief 获取vk::DescriptorImageInfo对象共享指针
     * @return 成功返回vk::DescriptorImageInfo共享对象指针，否则返回空指针
     */
    virtual std::shared_ptr<vk::DescriptorImageInfo>
    getDescriptorImageInfo(){
        return nullptr;
    }

protected:

    /**
     * @brief 准备与着色器代码的哪个binding关联
     */
    uint32_t m_descBinding;

    /**
     * @brief 与着色器代码binding关联的变量数组长度
     */
    uint32_t m_descBindingArrayLen;

    /**
     * @brief 对应的描述符类型
     */
    vk::DescriptorType m_descType;

    /**
     * @brief 准备用于哪个阶段的着色器
     */
    vk::ShaderStageFlags m_descStage;
}; // class IGetDecriptorInfo


//-------------------------------------------------------------------------

/**
 * @brief 描述符池， 用于生成描述布局、描述符集
 */
class DescriptorPool{

public:
    /**
     * @brief 构造描述符池
     * @param device 从哪个vulkan逻辑设备申请描述符池
     * @param poolCap 描述符池最多可建立的描述符容量数组，{存储缓冲,uniform纹素缓冲,uniform缓冲,纹素缓冲,存储图像}描述符数量向量
     * @param maxSetsCount  描述符池最多可建立的描述符集
     * @param flags 描述符池创建特性，可指定是否能回收资源、是否能绑定后更新描述符集
     */
    explicit DescriptorPool(std::shared_ptr<vk::Device> device,
                            const std::vector<uint32_t> & poolCap = {1},
                            uint32_t maxSetsCount = 1,
                            vk::DescriptorPoolCreateFlags flags = vk::DescriptorPoolCreateFlags()
                            );

    ~DescriptorPool();

    /**
     * @brief 复位整个描述符池（回收所有分配的描述符集资源），注意回收后以前创建的描述符集都不能够再使用
     */
    void resetDescriptorPool();

    /**
     * @brief 创建vulkan 描述符集布局，描述符集布局用于在描述符池中申请分配描述符集，返回的对象由调用方自行destroy()
     * @param descriptorSetBindings bindings数组， vk::DescriptorSetLayoutBinding(bind_idx, 类型, 数组长度, 着色阶段)
     * @return 返回vulkan描述符集布局对象共享指针，返回的对象由调用方自行destroy()
     */
    std::shared_ptr<vk::DescriptorSetLayout> createDescriptorSetLayout(
            const std::vector<vk::DescriptorSetLayoutBinding> & descriptorSetBindings
            );

    /**
     * @brief createDescriptorSetLayout 重载函数
     * @param infoGetters: 描述符信息获取接口指针数组
     * @param reorderBinding: 是否对binding号码重新从reorderOffset开始排序， 如果为false则使用从描述符信息获取接口获得的信息
     * @param reorderOffset:  重排序binding起始号码
     * @param stage: 如果为ShaderStageFlags(0)则使用从描述符信息获取接口获得的信息， 否则强制为指定的着色器阶段
     * @return
     */
    std::shared_ptr<vk::DescriptorSetLayout> createDescriptorSetLayout(
            std::vector<IGetDecriptorInfo *> infoGetters,
            bool reorderBinding = true,
            uint32_t reorderOffset = 0,
            vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eCompute
            );

    /**
     * @brief 销毁vulkan 描述符集布局
     * @param layout vulkan 描述符集布局
     */
    void destoryDescriptorSetLayout(std::shared_ptr<vk::DescriptorSetLayout> & layout);

    /**
     * @brief DescSetPair 描述符集及其对应布局pair类型
     */
    typedef std::pair<std::shared_ptr<vk::DescriptorSet>,
    std::shared_ptr<vk::DescriptorSetLayout>>
    DescSetPair;

    /**
     * @brief 重新创建相同描述符类型的描述符集(内部会同时创建描述符集布局)
     * @param count  描述符集里面包括多少个存储缓冲描述(多少个binding)
     * @param descSetPair  在哪个描述符集及其对应布局基础上进行回收和重新创建，如果为nullptr则会新建描述符集及其布局
     * @param type  描述符类型
     * @param stage 描述符集用于哪个着色阶段
     * @return  描述符集及其对应布局pair类型对象
     */
    DescSetPair recreateBufDescSet(uint32_t count,
                                   DescSetPair * descSetPair = nullptr,
                                   vk::DescriptorType type = vk::DescriptorType::eStorageBuffer,
                                   vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eCompute
            );


    /**
     * @brief 回收原有的描述符集，后根据给定的描述符集布局重新分配描述符集
     * @param layout 根据的描述符集布局
     * @param set  回收原有的描述符集
     * @return  成功返回新分配的描述符集共享指针， 否则返回空指针
     */
    std::shared_ptr<vk::DescriptorSet> recreateDescSet(
            vk::DescriptorSetLayout & layout,
            std::shared_ptr<vk::DescriptorSet> set = nullptr
            );

    /**
     * @brief recreateDescSet 重载版本，回收原有的描述符集，后根据给定的描述符集布局重新分配描述符集
     * @param descpair {回收原有的描述符集, 给定的描述符集布局} , 新分配的结果放到first（失败为nullptr）
     * @return 成功返回true, 否则返回false
     */
    bool recreateDescSet(DescSetPair & descpair);


    //    vk::WriteDescriptorSet(set,  // 写入的目标描述符集
    //                           binding, // 写入哪个绑定号变量, 例如(set = ?, binding = 0)就是0
    //                           0, // 如果绑定变量是数组，则指定从数组第几个元素开始写入信息
    //                           1, // 绑定变量不是数组一般为1; 更新的描述符数量，也就是下面三个指针信息重复写进dstArrayElement开始的多个数组描述符中，
    //                           DescriptorType, // 类型，指定uniform/image等buffer
    //                           pImageInfo , // 对于图像类型，要写入的信息源
    //                           pBufferInfo , //  对于storagebuffer, 要写入的信息源
    //                           pTexelBufferView,  //  对于纹理storagebuffer, 要写入的view信息源
    //                           ));
    /**
     * @brief 将描述信息写入到指定描述符集。vulkan描述符集申请后内容是空的要写入描述信息才能使用
     * @param writeInfo  写入的信息
     * @return 成功返回true, 失败返回false
     */
    bool writeDescriptorSet(const vk::WriteDescriptorSet & writeinfo);

    /**
     * @brief writeDescriptorSet 的重载版本，批量将信息写入多个描述符集
     * @param writeinfos  多个描述符集更新信息
     * @return 全部成功返回true, 失败返回false
     */
    bool writeDescriptorSet(const std::vector<vk::WriteDescriptorSet> & writeinfos);

    /**
     * @brief writeDescriptorSet 的重载版本，获取描述符集信息，批量将写入描述符集
     * @param dstSet  写入的目标描述符集
     * @param infoGetters  多个描述符集更新信息的获取接口指针
     * @return 全部成功返回true, 失败返回false
     */
    bool writeDescriptorSet(std::shared_ptr<vk::DescriptorSet> dstSet,
                            std::vector<IGetDecriptorInfo *> infoGetters);

protected:

    /**
     * @brief 创建描述符池
     * @param maxSetsCount
     * @param storageBufSize  描述符池最多可容纳多少个存储缓冲描述符
     * @param uniformTexelBufSize  描述符池最多可容纳多少个纹素uniform缓冲描述符
     * @param uniformBufSize  描述符池最多可容纳多少个uniform缓冲描述符
     * @param storageTexelBufSize  描述符池最多可容纳多少个纹素存储缓冲描述符
     * @param storageImgSize  描述符池最多可容纳多少个存储图像描述符
     */
    void createDescriptorPool(uint32_t maxSetsCount = 1,
                              uint32_t storageBufSize = 1,
                              uint32_t uniformTexelBufSize = 0,
                              uint32_t uniformBufSize = 0,
                              uint32_t storageTexelBufSize = 0,
                              uint32_t storageImgSize = 0);


    /**
     * @brief 从指定的描述符池中按指定布局申请vulkan描述符集(Set), 返回的对象由调用方自行destroy()
     * @param descpool  从描述符池中分配
     * @param layout  按描述符集分配
     * @return 返回vulkan描述符集对象共享指针，返回的对象由调用方自行destroy()
     */
    std::shared_ptr<vk::DescriptorSet> allocateDescriptorSet(
                std::shared_ptr<vk::DescriptorPool> descpool,
                std::shared_ptr<vk::DescriptorSetLayout> layout
                );

    /**
     * @brief vulkan 逻辑设备对象共享指针
     */
    std::shared_ptr<vk::Device> m_device;

    /**
     * @brief vulkan 描述符池对象共享指针
     */
    std::shared_ptr<vk::DescriptorPool> m_descPool;

    /**
     * @brief vulkan 描述符池对象创建标记
     */
    vk::DescriptorPoolCreateFlags m_descPoolCFlags;

    /**
     * @brief 从本类中创建的描述符集布局的资源记录
     */
    std::vector<std::shared_ptr<vk::DescriptorSetLayout>> m_descSetLayoutRes;
};  //kpext::DescriptorPool


} // namespace kpext



#endif // KPEXTDESCRIPTORMGR_H
